/*
 * 
 * Copyright (c) 2010 Mikhail Opletayev, http://opletayev.com

 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.

 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace Opletayev.Sync {

    internal delegate void SyncTaskHandler( SyncTask task );
    internal delegate void SyncTaskProgressHanlder( SyncTask task, int progress, string status );

    internal class SyncTaskGroup {
        internal int Outsanding = 1;
        internal System.Threading.ManualResetEvent Done;

        internal SyncTaskGroup() {
            Done = new ManualResetEvent( false );
        }

        internal void Release() {
            int count = 0;
            lock( this ) {
                count = --Outsanding;
            }
            if( count == 0 ) Done.Set();
        }

        internal void TaskComplete( SyncTask task ) {
            Release();
        }

        internal void Increment() {
            lock( this ) {
                Outsanding++;
            }
        }
    }

    internal class SyncTask {
        internal Control Control;
        internal SyncTaskGroup Group;
        internal object Result { get; set; }
        internal object Context { get; set; }
        internal Exception Exception;
        internal SyncTaskHandler Action { get; set; }
        internal SyncTaskHandler Complete { get; set; }
        internal SyncTaskHandler Finally { get; set; }
        internal SyncTaskHandler Error { get; set; }
        internal SyncTaskProgressHanlder Progress { get; set; }

        volatile bool IsCancelPending = false;

        internal SyncTask( Control ctrl ) {
            Control = ctrl;
        }

        internal SyncTask( Control ctrl, SyncTaskGroup group )
            : this( ctrl ) {
            Group = group;
        }

        private void ControlInvoke( SyncTaskHandler handler ) {
            if( Control != null ) Control.Invoke( handler, this );
            else handler( this );
        }

        internal void Cancel() {
            IsCancelPending = true;
        }

        internal bool IsCanceled {
            get {
                if( Control != null && Control.IsDisposed ) return true;
                return IsCancelPending;
            }
        }

        internal void DoAction() {
            if( Action != null ) Action( this );
        }

        internal void DoComplete() {
            if( Complete == null ) return;
            ControlInvoke( Complete );
        }

        internal void DoFinally() {
            if( Finally == null ) return;
            ControlInvoke( Finally );
        }

        internal void DoError() {
            if( Error == null ) return;
            try {
                ControlInvoke( Error );
            }
            catch { }  // this function should never throw an exception
        }

        internal void ReportProgress( int progress, string status ) {
            if( Progress == null ) return;
            if( Control != null ) Control.Invoke( Progress, this, progress, status );
            else Progress( this, progress, status );
        }
    }

    internal class TaskQueue {
        Queue<SyncTask> Tasks;
        System.Threading.AutoResetEvent evTask;
        int Outstanding = 0;

        volatile bool IsStopped = false;

        internal bool IsBusy {
            get {
                lock( this ) {
                    return Outstanding > 0;
                }
            }
        }

        internal TaskQueue( int capacity ) {
            Tasks = new Queue<SyncTask>();
            evTask = new AutoResetEvent( false );
            for( int i = 0; i < capacity; i++ ) {
                Thread t = new Thread( WorkerThread ) { Name = i.ToString(), IsBackground = true };
                t.Start();
            }
        }

        internal void Append( SyncTask task ) {
            int count = 0;
            lock( this ) {
                Tasks.Enqueue( task );
                count = Tasks.Count;
            }
            evTask.Set();
        }

        internal void Append( SyncTaskGroup group, SyncTask task ) {
            group.Increment();
            Append( task );
        }

        internal void Stop() {
            IsStopped = true;
            evTask.Set();
        }

        private void WorkerThread() {
            bool idle = true;
            try {
                while( true ) {
                    // close thread is queue is stopped
                    if( IsStopped ) break;
                    // fetch next pending task
                    SyncTask task = null;
                    lock( this ) {
                        if( Tasks.Count > 0 ) {
                            task = Tasks.Dequeue();
                            if( idle ) {
                                Outstanding++;
                                idle = false;
                            }
                        } else {
                            if( !idle ) {
                                Outstanding--;
                                idle = true;
                            }
                        }
                    }
                    // queue is empty, wait until something happens
                    if( task == null ) {
                        evTask.WaitOne( 1500, false );
                        if( IsStopped ) {
                            evTask.Set();
                            break;
                        }
                        continue;
                    }
                    // execute task action
                    try {
                        task.DoAction();
                        // this will freeze a working thread while the sub queries are executing
                        // a better way might be picking up another task to execute and having group
                        // posting back on the queue once all sub tasks are complete
                        // however, it most of the cases it makes sense to report completion immediately
                        // instead of waiting for the next available worker thread
                        if( task.Group != null ) {
                            while( true ) {
                                if( task.Group.Done.WaitOne( 1000, false ) ) break;
                                if( task.IsCanceled ) break;
                                if( IsStopped ) return;
                            }
                        }
                    }
                    catch( Exception ex ) {
                        task.Exception = ex;
                        task.DoError();
                    }
                    // task is complete
                    if( task.Exception == null && !task.IsCanceled ) {
                        try {
                            task.DoComplete();
                        }
                        catch( Exception ex ) {
                            task.Exception = ex;
                            task.DoError();
                        }
                    }
                    // call task finalizer
                    try {
                        if( !task.IsCanceled ) task.DoFinally();
                    }
                    catch( Exception ex ) {
                        task.Exception = ex;
                        task.DoError();
                    }
                }
            }
            catch( ThreadAbortException ) {
                // thread is aborted
            }
            catch( Exception ) {
                // critical exception
            }
        }
    }
}